import { Builder } from './Builder';
import { UsePlugin } from '../utils/usePlugin';
import { ConsoleMiddleware, ConsolePlugin } from '../plugins/Plugin';
import { PluginOption, OptionValidation } from '../plugins/PluginOption';
import { Validator } from '../validators/Validator';
import { PluginAction } from '../plugins/PluginAction';

interface Document {
  description?: string;
}

export class ConsoleBuilder<Props = any, State = any, Alias extends string = ''> extends Builder<Props, State> {
  protected readonly commands: string[];
  protected readonly document: Document;
  public/*protected*/ readonly pluginOptions: PluginOption;
  protected isShow: boolean = false;

  constructor(prefix: string, commands: string | string[]) {
    super();
    this.document = {};
    this.commands = (Array.isArray(commands) ? commands : [commands]).map((item) => prefix + item);
    // Don't register it until user call options().
    this.pluginOptions = new PluginOption();
  }

  public use<P, S>(plugin: UsePlugin<P, S> | ConsolePlugin<P, S>): ConsoleBuilder<Props & P, State & S> {
    this.plugins.use(plugin);
    return this;
  }

  public options<T extends { [key: string]: Validator }>(options: T): ConsoleBuilder<Props & OptionValidation<T>, State, Exclude<keyof T, symbol | number>> {
    this.plugins.use(this.pluginOptions, 'private-command-options');
    this.pluginOptions.setData(options);
    // @ts-expect-error
    return this;
  }

  public alias(optionAlias: { [key in Alias]?: string | string[] }): this {
    this.pluginOptions.setAlias(optionAlias);
    return this;
  }

  public action<P = {}, S = {}>(fn: ConsoleMiddleware<Props & P, State & S>): ConsoleBuilder<Props & P, State & S> {
    this.plugins.use(new PluginAction(fn));
    return this;
  }

  public showInHelp(show: boolean = true): this {
    this.isShow = show;
    return this;
  }

  public docs(document: Document): this {
    Object.assign(this.document, document);
    return this;
  }

  public/*protected*/ match(command: string): boolean {
    return this.commands.includes(command);
  }

  public/*protected*/ toJSON() {
    const aliases = this.pluginOptions.getAlias();

    return {
      commands: this.commands,
      description: this.document.description || '',
      showInHelp: this.isShow,
      options: Object.entries(this.pluginOptions.getData()).map(([key, validator]) => {
        const alias = aliases[key];
        const options = validator.toJSON();

        return {
          alias: alias === undefined
            ? undefined
            : Array.isArray(alias) ? alias : [alias],
          ...options,
          name: options.name || key,
        };
      }),
    };
  }
}
